From e5c5abfee0ead7ff1eef0d7572defdd06541a41a Mon Sep 17 00:00:00 2001 From: robertl Date: Fri, 16 Aug 2002 06:11:38 +0000 Subject: [PATCH] Add coldsync. --- coldsync/Makefile | 38 + coldsync/README | 211 ++++ coldsync/README.gpsbabel | 9 + coldsync/config.h | 8 + coldsync/palm.h | 53 + coldsync/pconn/util.h | 61 ++ coldsync/pdb.c | 2014 ++++++++++++++++++++++++++++++++++++++ coldsync/pdb.h | 273 ++++++ coldsync/util.c | 290 ++++++ mingw/Makefile | 3 +- 10 files changed, 2959 insertions(+), 1 deletion(-) create mode 100644 coldsync/Makefile create mode 100644 coldsync/README create mode 100644 coldsync/README.gpsbabel create mode 100644 coldsync/config.h create mode 100644 coldsync/palm.h create mode 100644 coldsync/pconn/util.h create mode 100644 coldsync/pdb.c create mode 100644 coldsync/pdb.h create mode 100644 coldsync/util.c diff --git a/coldsync/Makefile b/coldsync/Makefile new file mode 100644 index 000000000..c2d54597a --- /dev/null +++ b/coldsync/Makefile @@ -0,0 +1,38 @@ +# $Id: Makefile,v 1.1 2002/08/16 06:13:10 robertl Exp $ + +TOP = .. +SUBDIR = libpdb + +LIBNAME = pdb +SHLIB_MAJOR = 0 +SHLIB_MINOR = 0 + +LIBSRCS = pdb.c util.c +LIBOBJS = ${LIBSRCS:.c=.o} +SHLIBOBJS = ${LIBSRCS:.c=.So} + +CLEAN = ${LIBOBJS} ${SHLIBOBJS} ${LIBRARY} \ + *.ln *.bak *~ core *.core .depend +DISTCLEAN = +SPOTLESS = + +DISTFILES = Makefile ${LIBSRCS} + +OTHERTAGFILES = ${LIBSRCS} + +include ${TOP}/Make.rules + +depend:: + ${MKDEP} ${CPPFLAGS} ${LIBSRCS} + +all:: ${LIBRARY} + +# It might be a good idea later on to install the library, so that +# people can write other programs that communicate with the Palm, but +# not just yet. +# install:: + +# This is for Emacs's benefit: +# Local Variables: *** +# fill-column: 75 *** +# End: *** diff --git a/coldsync/README b/coldsync/README new file mode 100644 index 000000000..0d97b54aa --- /dev/null +++ b/coldsync/README @@ -0,0 +1,211 @@ + This is ColdSync, a tool for synchronizing data between Palm +devices and Unix workstations. + + Copyright (C) 1999-2001, Andrew Arensburger. + + The latest version of this package is available at + http://www.ooblick.com/software/coldsync/ + + This package is distributable under the terms of the Artistic +License. You should have received a file called "Artistic", which +specifies the terms under which this package may be distributed and +modified. + The Artistic License is taken from the Perl 5.005_03 +distribution, so some of the text is specific to Perl and does not +apply to ColdSync. I hope to address this in a future release. + + This product includes software developed by the University of +California, Berkeley and its contributors. + + This product includes software developed by the Apache Group +for use in the Apache HTTP server project (http://www.apache.org/). + (Actually, the Apache code in question was written by Panos +Tsirigotis. See comments in "src/ap_snprintf.c".) + + ---------------------------------------- + +* WHAT IS COLDSYNC? + + ColdSync is a tool for synchronizing data between Palm +computing devices (such as the PalmPilot, PalmPilot Pro, Palm V, +QualComm PDQ, Handspring Visor and so forth), and a Unix workstation. + ColdSync can back up and restore the state of a Palm, as well +as synchronize its data, which is sort of like a two-way rdist (see +below). In future versions, it will be possible to do more interesting +things with this data. + +* WHAT YOU'LL NEED + + - A POSIX-compliant operating system, preferably some flavor + of Unix + - An ANSI C compiler + - An ANSI C++ compiler + - Perl 5.005_03 or later (though earlier versions might work) + +* BUILDING AND INSTALLING COLDSYNC + + If you've built GNU software before, this should be familiar +territory. You should be able to just + + ./configure + make + make install + +Full details are provided in the "INSTALL" file. + +* WHAT IS SYNCHRONIZING? + + Synchronizing, also referred to as "syncing" refers to the +process of examining two databases (everything on the Palm is a +database) to see how they differ, and updating them so that they are +identical. + Syncing is different from just overwriting one database with +the other. For instance, if you add an entry for "Aunt Mabel" in your +Palm's address book, and an entry for "Uncle Bob" on your desktop +machine, then you don't want to just copy the address book from the +Palm to the desktop or vice-versa: that would delete one of the +entries that you just created. When you sync with ColdSync, you'll +wind up with both entries, on both the Palm and the desktop. + + Another difference between synchronizing and blind copying +lies in the fact that PalmOS has facilities to support syncing. If you +have 2000 entries in your Palm address book and want to copy them to +the desktop, it'll take a rather long time to copy them over a +relatively slow serial connection. When it syncs, ColdSync copies only +those records that have changed. + + ColdSync tries to be very cautious when it syncs, and not +delete any information unless it is sure that that is the right thing +to do. Its attitude is that it's better to err on the side of caution, +and maybe make you delete something twice, than it is to delete some +crucial bit of information. + +* SECURITY CONSIDERATIONS + + ColdSync is not secure. Period. + I have tried to pay due attention to security considerations, +but the sync process itself is inherently insecure. When ColdSync, +running on a workstation, receives a connection from a Palm, it has no +reliable way of knowing that the Palm on the other end is in fact the +one that it claims to be. + Likewise, when a Palm syncs with a workstation (whether that +workstation is using ColdSync or Palm's own HotSync), it has no +reliable way of knowing that the workstation is the one that it claims +to be. + PalmOS allows you to mark records as "private." This doesn't +mean a thing when you sync: the private and non-private records are +treated equally. In particular, anyone who has physical access to your +Palm can download your private records. + + There may be a Palm utility out there that will encrypt each +record in a database before a sync, but I don't know of any such +utility. + +* INTERNATIONALIZATION + + ColdSync includes some internationalization (i18n) support. It +is believed to work on all platforms with a Uniforum-compliant libintl +(gettext()). ColdSync does not work with XPG i18n (catgets() etc.). + However, you need GNU xgettext to compile the message catalog +("i18n/messages.po") from the source files. + + ---------------------------------------- + +COMPATIBILITY NOTES + +* ColdSync 2.2.4 + +FreeBSD: + This package was developed under FreeBSD 3.x/4.x, and compiles + cleanly with no modification under 4.2-RELEASE. + + On newer versions of FreeBSD (4.0 and beyond), it's possible + to communicate with the Handspring Visor using its USB + interface. Configure a listen type of "usb" rather than + "serial", and use device /dev/ugen0. + +Redhat Linux 6.2 (also Debian, and probably others): + (as of ColdSync v1.6.6-20010130) + Compiles cleanly with no modifications. Runs fine. + + The Linux serial device driver appears to drop characters at + random. As a result, you may see a lot of + + ##### Got an unexpected data packet. Sending an ACK to shut it up. + + messages. + +Solaris 2.7: + (as of v2.2.4) + There are still some problems with ColdSync's IPv6 code under + Solaris 2.6 and later. You'll need to use + ./configure --without-ipv6 + + (as of v2.2.0-20010805) + Compiles with Sun Forte 6U1. + + ColdSync is known not to work with Sun's i18n utilities. + ColdSync should detect this, and disable i18n. + +Digital Unix 4.0: + (as of ColdSync v2.4.4-20011113) + DEC's linker chokes on the overly-long identifiers produced by + the STL. GNU ld might work. + + (as of ColdSync v1.6.6-20010130) + Compiles with gcc 2.7.2. Compilation prints several warnings; + they appear to be benign: + + PConnection_serial.c:444: warning: implicit declaration of function `cfmakeraw' + PConnection_net.c:281: warning: passing arg 6 of `_Erecvfrom' from incompatiblepointer type + PConnection_net.c:674: warning: passing arg 6 of `_Erecvfrom' from incompatiblepointer type + PConnection_net.c:872: warning: passing arg 3 of `_Eaccept' from incompatible pointer type + config.c:964: warning: overflow in implicit constant conversion + GenericConduit.cc:82: warning: unused parameter `const struct conduit_block * block' + + If you are using DEC's C compiler, I suggest the following + compiler flags: + -std1 -msg_enable level3 + + +AIX 4.1: + (as of ColdSync v1.1.2) + Compiles with gcc 2.7.2, but when linking, complains that: + + ld: 0711-224 WARNING: Duplicate symbol: _IO_cleanup_registration_needed + ld: 0711-345 Use the -bloadmap or -bnoquiet option to obtain more information. + + This may be a problem with the installation, though (the same + thing happens when compiling "Hello, world"). + + I've only compiled it. I don't know whether it actually runs. + +If you have any updated information, please send it in to the +maintainer (arensb@ooblick.com). My testing pool isn't as large as it +once was. + +Windows NT: + (as of ColdSync 1.4.5) + To the best of my knowledge, ColdSync compiles and runs under + Windows NT with the Cygwin tools. + + However, ColdSync was written as a Unix tool. Windows users + have the HotSync desktop tools from Palm, which work quite + well. If ColdSync works under Windows, that's wonderful, but + I'm not going to let Windows compatibility get in the way of + Unix development. + +MacOS X: + (as of ColdSync 1.4.6) + + According to one correspondent, ColdSync compiles and runs + with no problems under MacOS X. + The serial port should be /dev/ttyd.printer . + + ---------------------------------------- + +BUGS: + If you create a Memo record, delete it without leaving the +editor, and check the "Save archive copy on PC" box, it will be +archived, but the archived record may contain trailing garbage. + This is due to a bug in PalmOS (as of 3.0). diff --git a/coldsync/README.gpsbabel b/coldsync/README.gpsbabel new file mode 100644 index 000000000..f99d8376a --- /dev/null +++ b/coldsync/README.gpsbabel @@ -0,0 +1,9 @@ +This directory is a subset of coldsync-2.2.5. GPSbabel needs very +limited set of that functionality to read and write Palm/OS files. +I was faced with either reimplementing it (and I DON'T want to +become a Palm/OS expert) or cribbing the code. Since it's all under +Artistic license, I took libpdb and the includes, whacked out the most +horribly non-portable pieces, jacked in a constand config.h, and pointed +the makefiles to it. + + diff --git a/coldsync/config.h b/coldsync/config.h new file mode 100644 index 000000000..5d489717a --- /dev/null +++ b/coldsync/config.h @@ -0,0 +1,8 @@ +/* + * Assume we're on a conformant ISO C platform. + */ + + +#define STDC_HEADERS 1 +#define _(str) str +// #define bzero(str, len) memset((str), 0, (len)) diff --git a/coldsync/palm.h b/coldsync/palm.h new file mode 100644 index 000000000..0bac9ec0c --- /dev/null +++ b/coldsync/palm.h @@ -0,0 +1,53 @@ +/* palm.h + * Definitions of various types that PalmOS likes to use. + * + * Copyright (C) 2001, Andrew Arensburger. + * You may distribute this file under the terms of the Artistic + * License, as specified in the README file. + * + * $Id: palm.h,v 1.1 2002/08/16 06:13:10 robertl Exp $ + */ +#ifndef _palm_h_ +#define _palm_h_ + +/* Convenience types */ +typedef signed char byte; /* Signed 8-bit quantity */ +typedef unsigned char ubyte; /* Unsigned 8-bit quantity */ +typedef signed short word; /* Signed 16-bit quantity */ +typedef unsigned short uword; /* Unsigned 16-bit quantity */ +typedef signed long dword; /* Signed 32-bit quantity */ +typedef unsigned long udword; /* Unsigned 32-bit quantity */ + +typedef udword chunkID; /* Those IDs made up of four + * characters stuck together into a + * 32-bit quantity. + */ + +/* Explicitly define the sizes of types. Can't depend on the host's types + * having the same size as the Palm. For instance, Alphas are 64-bit + * machines, so 'unsigned long' is 8 bytes, whereas 'udword' is only 4 + * bytes. + */ +#define SIZEOF_BYTE 1 +#define SIZEOF_UBYTE 1 +#define SIZEOF_WORD 2 +#define SIZEOF_UWORD 2 +#define SIZEOF_DWORD 4 +#define SIZEOF_UDWORD 4 + +/* MAKE_CHUNKID + * A convenience macro to make a chunkID out of four characters. + */ +#define MAKE_CHUNKID(a,b,c,d) \ + (((a) << 24) | \ + ((b) << 16) | \ + ((c) << 8) | \ + (d)) + +/* XXX - There ought to be something to make sure that the sizes and + * signedness above are true. + */ + +typedef enum { False = 0, True = 1 } Bool; + +#endif /* _palm_h_ */ diff --git a/coldsync/pconn/util.h b/coldsync/pconn/util.h new file mode 100644 index 000000000..2894165ff --- /dev/null +++ b/coldsync/pconn/util.h @@ -0,0 +1,61 @@ +/* util.h + * Misc. useful stuff. + * + * Copyright (C) 1999-2000, Andrew Arensburger. + * You may distribute this file under the terms of the Artistic + * License, as specified in the README file. + * + * $Id: util.h,v 1.1 2002/08/16 06:14:09 robertl Exp $ + */ +#ifndef _util_h_ +#define _util_h_ + +#include +#include +#include "palm.h" +// #include "dlp_cmd.h" + +/* XXX - The functions declared INLINE, below, really ought to be inline + * functions. I'm not sure how to do this portably, though. + */ +#ifdef __GNUC__ +# define INLINE __inline__ +#else +# define INLINE +#endif /* __GNUC__ */ + +/* Functions for reading a value from an array of ubytes */ +extern INLINE ubyte peek_ubyte(const ubyte *buf); +extern INLINE uword peek_uword(const ubyte *buf); +extern INLINE udword peek_udword(const ubyte *buf); + +/* Functions for extracting values from an array of ubytes */ +extern INLINE ubyte get_ubyte(const ubyte **buf); +extern INLINE uword get_uword(const ubyte **buf); +extern INLINE udword get_udword(const ubyte **buf); + +/* Functions for writing values to an array of ubytes */ +extern INLINE void put_ubyte(ubyte **buf, const ubyte value); +extern INLINE void put_uword(ubyte **buf, const uword value); +extern INLINE void put_udword(ubyte **buf, const udword value); + +#if TIME +/* Functions for converting between DLP's time format and Unix's + * time_ts and the time_t-with-offset that the rest of the Palm stuff + * uses. + */ +extern time_t time_dlp2time_t(const struct dlp_time *dlpt); +extern udword time_dlp2palmtime(const struct dlp_time *dlpt); +extern void time_time_t2dlp(const time_t t, struct dlp_time *dlpt); +extern void time_palmtime2dlp(const udword palmt, struct dlp_time *dlpt); + +extern void debug_dump(FILE *outfile, const char *prefix, + const ubyte *buf, const udword len); +#endif +#endif /* _util_h_ */ + + /* This is for Emacs's benefit: + * Local Variables: *** + * fill-column: 75 *** + * End: *** + */ diff --git a/coldsync/pdb.c b/coldsync/pdb.c new file mode 100644 index 000000000..ece0c958f --- /dev/null +++ b/coldsync/pdb.c @@ -0,0 +1,2014 @@ +/* pdb.c + * + * Functions for dealing with Palm databases and such. + * + * Copyright (C) 1999-2001, Andrew Arensburger. + * You may distribute this file under the terms of the Artistic + * License, as specified in the README file. + * + * $Id: pdb.c,v 1.1 2002/08/16 06:13:10 robertl Exp $ + */ +/* XXX - The way zero-length records are handled is a bit of a kludge. They + * shouldn't normally exist, with the exception of expunged records. But, + * of course, a malformed conduit or something can create them. + * The half-assed way they're handled here is to a) not upload zero-length + * records to the Palm, b) warn the user if they're written to a file, c) + * provide a utility (in the p5-Palm package) to delete zero-length + * records. + */ +/* XXX - This is a library. It shouldn't print error messages. + * Add 'int pdb_errno'; define error numbers and error messages that go + * with them. + * Debugging messages should go to 'FILE *pdb_logfile'. + */ + +#include "config.h" +#include +#include /* For open() */ +#include +// #include +// #include /* For MAXPATHLEN */ +#include +#include +#include + +#if STDC_HEADERS +# include /* For strncat(), memcpy() et al. */ +#else /* STDC_HEADERS */ +# ifndef HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif /* HAVE_STRCHR */ +# ifndef HAVE_MEMCPY +# define memcpy(d,s,n) bcopy ((s), (d), (n)) +# define memmove(d,s,n) bcopy ((s), (d), (n)) +# endif /* HAVE_MEMCPY */ +#endif /* STDC_HEADERS */ + +/* XXX - Is this right? Should this be in the "else" clause, above? */ +#if HAVE_STRINGS_H +# include /* For bzero() */ +#endif /* HAVE_STRINGS_H */ + +#if HAVE_LIBINTL_H +# include /* For i18n */ +#endif /* HAVE_LIBINTL_H */ + +#include +#include "pdb.h" + +/* XXX - The functions declared INLINE, below, really ought to be inline + * functions. I'm not sure how to do this portably, though. + */ +#ifdef __GNUC__ +# define INLINE __inline__ +#else +# define INLINE +#endif /* __GNUC__ */ + +/* Functions for extracting values from an array of ubytes */ +extern INLINE ubyte get_ubyte(const ubyte **buf); +extern INLINE uword get_uword(const ubyte **buf); +extern INLINE udword get_udword(const ubyte **buf); + +/* Functions for writing values to an array of ubytes */ +extern INLINE void put_ubyte(ubyte **buf, const ubyte value); +extern INLINE void put_uword(ubyte **buf, const uword value); +extern INLINE void put_udword(ubyte **buf, const udword value); +extern void debug_dump(FILE *outfile, const char *prefix, + const ubyte *buf, const udword len); + +int pdb_trace = 0; /* Debugging level for PDB stuff */ +#define PDB_TRACE(n) if (pdb_trace >= (n)) + +/* Helper functions */ +static uword get_file_length(int fd); +int pdb_LoadHeader(int fd, struct pdb *db); + /* pdb_LoadHeader() is visible to other files */ +static int pdb_LoadRecListHeader(int fd, struct pdb *db); +static int pdb_LoadRsrcIndex(int fd, struct pdb *db); +static int pdb_LoadRecIndex(int fd, struct pdb *db); +static int pdb_LoadAppBlock(int fd, struct pdb *db); +static int pdb_LoadSortBlock(int fd, struct pdb *db); +static int pdb_LoadResources(int fd, struct pdb *db); +static int pdb_LoadRecords(int fd, struct pdb *db); + +/* merge_attributes + * Takes a record's flags and category, and merges them into a single byte, + * with the flags in the top nybble and the category in the bottom one + */ +static inline ubyte +merge_attributes(const ubyte flags, + const ubyte category) +{ + /* The PDB_REC_ARCHIVED flag is troublesome, since it overlaps the + * category field. The idea here is that if the record was deleted, + * then it doesn't have a category anymore, so the category part + * gets set to 0. + */ + if ((flags & PDB_REC_DELETED) == 0) + return (flags & 0xf0) | + (category & 0x0f); + else + return (flags & 0xf8); +} + +/* split_attributes + * The converse of merge_attributes(). Takes the combined field attributes + * and writes its contents to *flags and *category, using the same rules as + * merge_attributes(), above. + */ +static inline void +split_attributes(const ubyte attributes, + ubyte *flags, + ubyte *category) +{ + if ((attributes & PDB_REC_DELETED) == 0) + { + *flags = (attributes & 0xf0); + *category = (attributes & 0x0f); + } else { + *flags = (attributes & 0xf8); + *category = 0; + } + PDB_TRACE(6) + fprintf(stderr, "split 0x%02x into 0x%02x, 0x%02x\n", + attributes, *flags, *category); +} + +/* new_pdb + * struct pdb constructor. + */ +struct pdb * +new_pdb() +{ + struct pdb *retval; + + /* Allocate the new pdb */ + if ((retval = (struct pdb *) malloc(sizeof(struct pdb))) == NULL) + /* Out of memory */ + return NULL; + + /* Write zeros all over it, just for safety */ + memset((void *) retval, 0, sizeof(struct pdb)); + + return retval; +} + +/* pdb_FreeRecord + * Free a previously-allocated 'pdb_record'. This function wouldn't really + * be necessary, except that pdb_CopyRecord() returns a 'pdb_record'. + */ +void +pdb_FreeRecord(struct pdb_record *rec) +{ + if (rec->data != NULL) + free(rec->data); + free(rec); +} + +/* pdb_FreeResource + * Free a previously-allocated 'pdb_resource'. This function wouldn't + * really be necessary, except that pdb_CopyResource() returns a + * 'pdb_resource'. + */ +void +pdb_FreeResource(struct pdb_resource *rsrc) +{ + if (rsrc->data != NULL) + free(rsrc->data); + free(rsrc); +} + +/* free_pdb + * Cleanly free a struct pdb, and all of its subparts (destructor). + */ +void +free_pdb(struct pdb *db) +{ + PDB_TRACE(7) + fprintf(stderr, "Inside free_pdb(%p)\n", (void *) db); + + if (db == NULL) + /* Trivial case */ + return; + + /* Free the array of records/resources */ + if (IS_RSRC_DB(db)) + { + /* It's a resource database */ + struct pdb_resource *rsrc; + struct pdb_resource *next; + + PDB_TRACE(8) + fprintf(stderr, "Freeing resource list\n"); + + /* Walk the linked list, freeing as we go along */ + for (rsrc = db->rec_index.rsrc; + rsrc != NULL; + rsrc = next) + { + next = rsrc->next; /* Remember the next + * element on the list. We + * won't have a chance to + * look it up after this + * one has been free()d. + */ + + /* Free this element */ + pdb_FreeResource(rsrc); + } + } else { + /* It's a record database */ + struct pdb_record *rec; + struct pdb_record *next; + + PDB_TRACE(8) + fprintf(stderr, "Freeing record list\n"); + + /* Walk the linked list, freeing as we go along */ + for (rec = db->rec_index.rec; + rec != NULL; + rec = next) + { + next = rec->next; /* Remember the next + * element on the list. We + * won't have a chance to + * look it up after this + * one has been free()d. + */ + + /* Free this element */ + pdb_FreeRecord(rec); + } + } + + /* Free the sort block */ + if (db->sortinfo != NULL) + free(db->sortinfo); + + /* Free the app info block */ + if (db->appinfo != NULL) + free(db->appinfo); + + free(db); +} + +/* pdb_Read + * Read a PDB from the file descriptor 'fd'. This must already have been + * opened for reading and/or writing. + * + * Note: this function does not to any locking. The caller is responsible + * for that. + */ +struct pdb * +pdb_Read(int fd) +{ + int err; + struct pdb *retval; + + /* Create a new pdb to return */ + if ((retval = new_pdb()) == NULL) + { + return NULL; + } + + /* Find out how long the file is */ + retval->file_size = get_file_length(fd); + if (retval->file_size == ~0) + { + /* The file isn't seekable */ + fprintf(stderr, _("File isn't seekable.\n")); + free_pdb(retval); + return NULL; + } + + /* Load the header */ + if ((err = pdb_LoadHeader(fd, retval)) < 0) + { + fprintf(stderr, _("Can't load header.\n")); + free_pdb(retval); + return NULL; + } + + /* Load the record list header */ + if ((err = pdb_LoadRecListHeader(fd, retval)) < 0) + { + fprintf(stderr, _("Can't load record list header for " + "\"%.*s\".\n"), + PDB_DBNAMELEN, retval->name); + free_pdb(retval); + return NULL; + } + + /* Read the record/resource list */ + if (IS_RSRC_DB(retval)) + { + /* Read the resource index */ + if ((err = pdb_LoadRsrcIndex(fd, retval)) < 0) + { + fprintf(stderr, _("Can't read resource index for " + "\"%.*s\".\n"), + PDB_DBNAMELEN, retval->name); + free_pdb(retval); + return NULL; + } + } else { + /* Read the record index */ + if ((err = pdb_LoadRecIndex(fd, retval)) < 0) + { + fprintf(stderr, _("Can't read record index for " + "\"%.*s\".\n"), + PDB_DBNAMELEN, retval->name); + free_pdb(retval); + return NULL; + } + } + + /* In most PDBs, there are two NUL bytes here. They are allowed by + * the spec, but not mandated, and some PDBs don't have them. We'll + * ignore them for now, and have the appropriate pdb_Load*() + * function lseek() to the proper position. + */ + + /* Load the AppInfo block, if any */ + if ((err = pdb_LoadAppBlock(fd, retval)) < 0) + { + fprintf(stderr, _("Can't read AppInfo block for " + "\"%.*s\".\n"), + PDB_DBNAMELEN, retval->name); + free_pdb(retval); + return NULL; + } + + /* Load the sort block, if any */ + if ((err = pdb_LoadSortBlock(fd, retval)) < 0) + { + fprintf(stderr, _("Can't read sort block for " + "\"%.*s\".\n"), + PDB_DBNAMELEN, retval->name); + free_pdb(retval); + return NULL; + } + + /* Load the records themselves */ + if (IS_RSRC_DB(retval)) + { + /* Read the resources */ + if ((err = pdb_LoadResources(fd, retval)) < 0) + { + fprintf(stderr, _("Can't read resources for " + "\"%.*s\".\n"), + PDB_DBNAMELEN, retval->name); + free_pdb(retval); + return NULL; + } + } else { + /* Read the records */ + if ((err = pdb_LoadRecords(fd, retval)) < 0) + { + fprintf(stderr, _("Can't read records for " + "\"%.*s\".\n"), + PDB_DBNAMELEN, retval->name); + free_pdb(retval); + return NULL; + } + } + + return retval; /* Success */ +} + +/* pdb_Write + * Write 'db' to the file descriptor 'fd'. This must already have been + * opened for writing. + * + * Note that while you can open the backup file for reading and writing, + * read from it with pdb_Read() and save it with pdb_Write(), this is not + * recommended: if anything should go wrong at the wrong time (e.g., the + * disk fills up just as you're about to write the database back to disk), + * you will lose the entire backup. + * A better approach is to use a staging file: read from the backup file, + * write to a temporary file, then use rename() to move the temporary file + * onto the real one. Alternately, you can copy the original file to a + * temporary one, then open the temporary for both reading and writing. + * This might have some advantages, in that it allows you to lock a single + * file for the duration of the sync. + * + * Note: this function does not lock the file. The caller is responsible + * for that. + */ +int +pdb_Write(const struct pdb *db, + int fd) +{ + static ubyte header_buf[PDB_HEADER_LEN]; + /* Buffer for writing database header */ + static ubyte rlheader_buf[PDB_RECORDLIST_LEN]; + /* Buffer for writing the record list header */ + static ubyte nul_buf[2]; + /* Buffer for writing the two useless NULs */ + ubyte *wptr; /* Pointer into buffers, for writing */ + udword offset; /* The next offset we're interested in */ + + /* Initialize 'offset': the next variable-sized item will go after + * the header, after the index header, after the index, after the + * two useless NULs. + */ + offset = PDB_HEADER_LEN + PDB_RECORDLIST_LEN; + if (IS_RSRC_DB(db)) + offset += db->numrecs * PDB_RESOURCEIX_LEN; + else + offset += db->numrecs * PDB_RECORDIX_LEN; + offset += 2; /* Those two useless NUL bytes */ + + /** Write the database header **/ + + /* Construct the header in 'header_buf' */ + wptr = header_buf; + memcpy(wptr, db->name, PDB_DBNAMELEN); + wptr += PDB_DBNAMELEN; + put_uword(&wptr, (db->attributes & ~PDB_ATTR_OPEN)); + /* Clear the 'open' flag before writing */ + put_uword(&wptr, db->version); + put_udword(&wptr, db->ctime); + put_udword(&wptr, db->mtime); + put_udword(&wptr, db->baktime); + put_udword(&wptr, db->modnum); + if (db->appinfo == NULL) /* Write the AppInfo block, if any */ + /* This database doesn't have an AppInfo block */ + put_udword(&wptr, 0L); + else { + /* This database has an AppInfo block */ + put_udword(&wptr, offset); + offset += db->appinfo_len; + } + if (db->sortinfo == NULL) /* Write the sort block, if any */ + /* This database doesn't have a sort block */ + put_udword(&wptr, 0L); + else { + put_udword(&wptr, offset); + offset += db->sortinfo_len; + } + put_udword(&wptr, db->type); + put_udword(&wptr, db->creator); + put_udword(&wptr, db->uniqueIDseed); + + /* Write the database header */ + if (write(fd, header_buf, PDB_HEADER_LEN) != PDB_HEADER_LEN) + { + fprintf(stderr, _("%s: can't write database header for " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + close(fd); + return -1; + } + + /** Write the record/resource index header **/ + /* Construct the record list header */ + wptr = rlheader_buf; + put_udword(&wptr, 0L); /* nextID */ + /* XXX - What is this? Should this be something + * other than 0? */ + put_uword(&wptr, db->numrecs); + + /* Write the record list header */ + if (write(fd, rlheader_buf, PDB_RECORDLIST_LEN) != PDB_RECORDLIST_LEN) + { + fprintf(stderr, _("%s: can't write record list header for " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + + /* Write the record/resource index */ + if (IS_RSRC_DB(db)) + { + /* It's a resource database */ + struct pdb_resource *rsrc; /* Current resource */ + + /* Go through the list of resources, writing each one */ + for (rsrc = db->rec_index.rsrc; + rsrc != NULL; + rsrc = rsrc->next) + { + static ubyte rsrcbuf[PDB_RESOURCEIX_LEN]; + /* Buffer to hold the resource + * index entry. + */ + + /* Construct the resource index entry */ + wptr = rsrcbuf; + put_udword(&wptr, rsrc->type); + put_uword(&wptr, rsrc->id); + put_udword(&wptr, offset); + + /* Write the resource index entry */ + if (write(fd, rsrcbuf, PDB_RESOURCEIX_LEN) != + PDB_RESOURCEIX_LEN) + { + fprintf(stderr, _("%s: Can't write resource " + "index entry for " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + + /* Bump 'offset' up to point to the offset of the + * next variable-sized thing in the file. + */ + offset += rsrc->data_len; + } + } else { + /* It's a record database */ + struct pdb_record *rec; /* Current record */ + + /* Go through the list of records, writing each one */ + for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) + { + static ubyte recbuf[PDB_RECORDIX_LEN]; + /* Buffer to hold the record index + * entry. + */ + + /* Construct the record index entry */ + wptr = recbuf; + + /* Sanity check */ + if (rec->data_len == 0) + { + fprintf(stderr, + _("\"%.*s\" record 0x%08lx has " + "length 0.\n"), + PDB_DBNAMELEN, db->name, + rec->id); + } + + put_udword(&wptr, offset); + put_ubyte(&wptr, merge_attributes( + rec->flags, + rec->category)); + put_ubyte(&wptr, (char) ((rec->id >> 16) & 0xff)); + put_ubyte(&wptr, (char) ((rec->id >> 8) & 0xff)); + put_ubyte(&wptr, (char) (rec->id & 0xff)); + + /* Write the resource index entry */ + if (write(fd, recbuf, PDB_RECORDIX_LEN) != + PDB_RECORDIX_LEN) + { + fprintf(stderr, _("%s: Can't write record " + "index entry for " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + + /* Bump 'offset' up to point to the offset of the + * next variable-sized thing in the file. + */ + offset += rec->data_len; + } + } + + /* Write the two useless NUL bytes */ + nul_buf[0] = nul_buf[1] = '\0'; + if (write(fd, nul_buf, 2) != 2) + { + fprintf(stderr, _("%s: Can't write the two useless NULs to " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + + /* Write the AppInfo block, if any */ + if (db->appinfo != NULL) + { + if (write(fd, db->appinfo, db->appinfo_len) != + db->appinfo_len) + { + fprintf(stderr, _("%s: Can't write AppInfo block for " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + } + + /* Write the sort block, if any */ + if (db->sortinfo != NULL) + { + if (write(fd, db->sortinfo, db->sortinfo_len) != + db->sortinfo_len) + { + fprintf(stderr, _("%s: Can't write sort block for " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + } + + /* Write the record/resource data */ + if (IS_RSRC_DB(db)) + { + /* It's a resource database */ + struct pdb_resource *rsrc; + + /* Go through the list of resources, writing each one's + * data. + */ + for (rsrc = db->rec_index.rsrc; + rsrc != NULL; + rsrc = rsrc->next) + { + /* Write the data */ + if (write(fd, rsrc->data, rsrc->data_len) != + rsrc->data_len) + { + fprintf(stderr, _("%s: Can't write resource " + "data for \"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + } + } else { + /* It's a record database */ + struct pdb_record *rec; + + /* Go through the list of records, writing each one's data. */ + for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) + { + /* Write the data */ + if (write(fd, rec->data, rec->data_len) != + rec->data_len) + { + fprintf(stderr, + _("%s: Can't write record data for " + "\"%.*s\".\n"), + "pdb_Write", + PDB_DBNAMELEN, db->name); + perror("write"); + return -1; + } + } + } + + return 0; /* Success */ +} + +/* pdb_FindRecordByID + * Find the record in 'db' whose ID is 'id'. Return a pointer to it. If no + * such record exists, or in case of error, returns NULL. + */ +struct pdb_record * +pdb_FindRecordByID( + const struct pdb *db, + const udword id) +{ + struct pdb_record *rec; + + /* Walk the list of records, comparing IDs */ + for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) + { + if (rec->id == id) + return rec; + } + + return NULL; /* Couldn't find it */ +} + +/* pdb_FindRecordByIndex + * Find the 'index'th record in 'db', and return a pointer to it. If no + * such record exists, or in case of error, return NULL. + */ +struct pdb_record * +pdb_FindRecordByIndex( + const struct pdb *db, /* Database to look in */ + const uword index) /* Index of the record to look for */ +{ + struct pdb_record *rec; + int i; + + /* Walk the list, decrementing the count as we go along. If it + * reaches 0, we've found the record. + */ + rec = db->rec_index.rec; + for (i = index; i > 0; i--) + { + if (rec == NULL) + /* Oops! We've fallen off the end of the list */ + return NULL; + rec = rec->next; + } + + return rec; /* Success */ +} + +/* pdb_NextRecord + * Find the next record after 'rec' in 'db', and return a pointer to it. If + * 'rec' is the last record in the list, return NULL. + */ +struct pdb_record * +pdb_NextRecord(const struct pdb *db, /* Database to look in */ + const struct pdb_record *rec) + /* Return 'rec's successor */ +{ + return rec->next; +} + +/* pdb_DeleteRecordByID + * Find the record whose unique ID is 'id' and delete it from 'db'. If the + * record isn't found, well, that's okay; we wanted to delete it anyway. + * Returns 0 if successful, -1 in case of error. + */ +int +pdb_DeleteRecordByID( + struct pdb *db, + const udword id) +{ + struct pdb_record *rec; /* Record we're looking at */ + struct pdb_record *last; /* Last record we saw */ + + if (IS_RSRC_DB(db)) + /* This only works with record databases */ + return -1; + + /* Look through the list of records */ + last = NULL; /* Haven't seen any records yet */ + for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) + { + /* See if the ID matches */ + if (rec->id == id) + { + /* Found it */ + + /* XXX - Presumably better to use pdb_FreeRecord() */ + /* Free 'rec's data */ + if (rec->data != NULL) + free(rec->data); + + /* Cut 'rec' out of the list. The first element of + * the list is a special case. + */ + if (last == NULL) + db->rec_index.rec = rec->next; + else + last->next = rec->next; + + free(rec); /* Free it */ + db->numrecs--; /* Decrement record count */ + + return 0; /* Success */ + } + + last = rec; /* Remember what we just saw */ + } + + /* Couldn't find it. Oh, well. Call it a success anyway. */ + return 0; +} + +/* pdb_AppendRecord + * Append a new record to 'db's record list. 'newrec' is not copied, so it + * is important that the caller not free it afterwards. + */ +/* XXX - Ought to make sure that the ID is unique */ +int +pdb_AppendRecord(struct pdb *db, + struct pdb_record *newrec) +{ + struct pdb_record *rec; + + /* Sanity check */ + if (IS_RSRC_DB(db)) + /* This only works with record databases */ + return -1; + + /* Check to see if the list is empty */ + if (db->rec_index.rec == NULL) + { + db->rec_index.rec = newrec; + newrec->next = NULL; + + db->numrecs++; /* Bump record counter */ + + return 0; /* Success */ + } + + /* Walk the list to find its end */ + for (rec = db->rec_index.rec; rec->next != NULL; rec = rec->next) + ; + rec->next = newrec; + newrec->next = NULL; + + db->numrecs++; /* Bump record counter */ + + return 0; /* Success */ +} + +/* pdb_AppendResource + * Append a new resource to 'db's resource list. 'newrsrc' is not copied, + * so it is important that the caller not free it afterwards. + */ +int +pdb_AppendResource(struct pdb *db, + struct pdb_resource *newrsrc) +{ + struct pdb_resource *rsrc; + + /* Sanity check */ + if (!IS_RSRC_DB(db)) + /* This only works with resource databases */ + return -1; + + /* Check to see if the list is empty */ + if (db->rec_index.rsrc == NULL) + { + db->rec_index.rsrc = newrsrc; + newrsrc->next = NULL; + + db->numrecs++; /* Bump resource counter */ + + return 0; /* Success */ + } + + /* Walk the list to find its end */ + for (rsrc = db->rec_index.rsrc; rsrc->next != NULL; rsrc = rsrc->next) + ; + rsrc->next = newrsrc; + newrsrc->next = NULL; + + db->numrecs++; /* Bump resource counter */ + + return 0; /* Success */ +} + +/* pdb_InsertRecord + * Insert 'newrec' into 'db', just after 'prev'. If 'prev' is NULL, + * 'newrec' is inserted at the beginning of the list. + * Returns 0 if successful, -1 otherwise. + * 'newrec' is not copied, so it is important that the caller not free it. + */ +int +pdb_InsertRecord(struct pdb *db, /* The database to insert into */ + struct pdb_record *prev, + /* Insert after this record */ + struct pdb_record *newrec) + /* The record to insert */ +{ + /* If 'prev' is NULL, insert at the beginning of the list */ + if (prev == NULL) + { + newrec->next = db->rec_index.rec; + db->rec_index.rec = newrec; + db->numrecs++; /* Increment record count */ + + return 0; /* Success */ + } + + /* XXX - This function doesn't actually check to make sure that + * 'prev' is in 'db'. You could really fuck yourself over with + * this. + * So make it a documented requirement. + */ + /* The new record goes in the middle of the list. Insert it. */ + newrec->next = prev->next; + prev->next = newrec; + db->numrecs++; /* Increment record count */ + + return 0; /* Success */ +} + +/* pdb_InsertResource + * Insert 'newrsrc' into 'db', just after 'prev'. If 'prev' is NULL, 'newrsrc' + * is inserted at the beginning of the list. + * Returns 0 if successful, -1 otherwise. + * 'newrec' is not copied, so it is important that the caller not free it. + */ +int +pdb_InsertResource(struct pdb *db, /* The database to insert into */ + struct pdb_resource *prev, + /* Insert after this resource */ + struct pdb_resource *newrsrc) + /* The resource to insert */ +{ + /* If 'prev' is NULL, insert at the beginning of the list */ + if (prev == NULL) + { + newrsrc->next = db->rec_index.rsrc; + db->rec_index.rsrc = newrsrc; + db->numrecs++; /* Increment record count */ + + return 0; /* Success */ + } + + /* XXX - This function doesn't actually check to make sure that + * 'prev' is in 'db'. You could really fuck yourself over with + * this. + * So make it a documented requirement. + */ + /* The new resource goes in the middle of the list. Insert it. */ + newrsrc->next = prev->next; + prev->next = newrsrc; + db->numrecs++; /* Increment record count */ + + return 0; /* Success */ +} + +/* new_Record + * Create a new record from the given arguments, and return a pointer to + * it. Returns NULL in case of error. + * The record data is copied, so the caller needs to take care of freeing + * 'data'. + * + * The 'attributes' and 'category' arguments are combined into one field: + * if the new record is deleted, then the category is silently dropped. + * Otherwise, the category occupies the bottom 4 bits of the + * attributes/category field. + */ +struct pdb_record * +new_Record(const ubyte flags, + const ubyte category, + const udword id, + const uword len, + const ubyte *data) +{ + struct pdb_record *retval; + + PDB_TRACE(6) + { + fprintf(stderr, "new_Record: Creating new record:\n"); + fprintf(stderr, "\tflags == 0x%02x\n", flags); + fprintf(stderr, "\tcategory == 0x%02x\n", category); + fprintf(stderr, "\tid == 0x%08lx\n", id); + fprintf(stderr, "\tlen == %d\n", len); + debug_dump(stderr, "NEW", data, len); + } + + /* Allocate the record to be returned */ + if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record))) + == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "new_Record"); + return NULL; + } + + /* Initialize the new record */ + retval->next = NULL; + retval->offset = 0L; + retval->flags = flags; + retval->category = category; + retval->id = id; + + /* Allocate space to put the record data */ + if (len == 0) + { + /* Special case: the record has no data (e.g., this is an + * expunged record). + */ + retval->data_len = len; + retval->data = NULL; + return retval; + } + + if ((retval->data = (ubyte *) malloc(len)) == NULL) + { + /* Couldn't allocate data portion of record */ + fprintf(stderr, _("%s: can't allocate data.\n"), + "new_Record"); + free(retval); + return NULL; + } + + /* Copy the data to the new record */ + retval->data_len = len; + memcpy(retval->data, data, len); + + return retval; /* Success */ +} + +/* new_Resource + * Create a new resource from the given arguments, and return a pointer to + * it. Returns NULL in case of error. + * The resource data is copied, so the caller needs to take care of freeing + * 'data'. + */ +struct pdb_resource * +new_Resource(const udword type, + const uword id, + const uword len, + const ubyte *data) +{ + struct pdb_resource *retval; + + PDB_TRACE(6) + { + fprintf(stderr, "new_Resource: Creating new resource:\n"); + fprintf(stderr, "\ttype == 0x%08lx (%c%c%c%c)\n", + type, + (int) ((type >> 24) & 0xff), + (int) ((type >> 16) & 0xff), + (int) ((type >> 8) & 0xff), + (int) (type & 0xff)); + fprintf(stderr, "\tid == 0x%04x\n", id); + fprintf(stderr, "\tlen == %d\n", len); + debug_dump(stderr, "NEW", data, len); + } + + /* Allocate the resource to be returned */ + if ((retval = (struct pdb_resource *) + malloc(sizeof(struct pdb_resource))) == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "new_Resource"); + return NULL; + } + + /* Initialize the new resource */ + retval->next = NULL; + retval->offset = 0L; + retval->type = type; + retval->id = id; + + /* Allocate space to put the resource data */ + if (len == 0) + { + /* Special case: zero-length resource (dunno if this should + * ever happen, but this way we avoid malloc(0). + */ + retval->data_len = len; + retval->data = NULL; + return retval; + } + + if ((retval->data = (ubyte *) malloc(len)) == NULL) + { + /* Couldn't allocate data portion of resource */ + fprintf(stderr, _("%s: can't allocate data.\n"), + "new_Resource"); + free(retval); + return NULL; + } + + /* Copy the data to the new resource */ + retval->data_len = len; + memcpy(retval->data, data, len); + + return retval; /* Success */ +} + +/* pdb_CopyRecord + * Make a copy of record 'rec' in database 'db' (and its data), and return + * it. The new record is allocated by pdb_CopyRecord(), so the caller has + * to take care of freeing it. + * Returns a pointer to the new copy, or NULL in case of error. + */ +struct pdb_record *pdb_CopyRecord( + const struct pdb *db, + const struct pdb_record *rec) +{ + struct pdb_record *retval; + + /* Allocate the record to be returned */ + if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record))) + == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "pdb_CopyRecord"); + return NULL; + } + + retval->next = NULL; /* For cleanliness */ + + /* Copy the old record to the new copy */ + retval->offset = rec->offset; + retval->flags = rec->flags; + retval->category = rec->category; + retval->id = rec->id; + + /* Allocate space for the record data itself */ + if ((retval->data = (ubyte *) malloc(rec->data_len)) == NULL) + { + fprintf(stderr, _("%s: can't allocate record data for " + "\"%.*s\".\n"), + "pdb_CopyRecord", + PDB_DBNAMELEN, db->name); + free(retval); + return NULL; + } + + /* Copy the record data */ + retval->data_len = rec->data_len; + memcpy(retval->data, rec->data, retval->data_len); + + return retval; /* Success */ +} + +/* pdb_CopyResource + * Make a copy of resource 'rsrc' in database 'db' (and its data), and + * return it. The new record is allocated by pdb_CopyResource(), so the + * caller has to take care of freeing it. + * Returns a pointer to the new copy, or NULL in case of error. + */ +struct pdb_resource *pdb_CopyResource( + const struct pdb *db, + const struct pdb_resource *rsrc) +{ + struct pdb_resource *retval; + + /* Allocate the resource to be returned */ + if ((retval = (struct pdb_resource *) + malloc(sizeof(struct pdb_resource))) == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "pdb_CopyResource"); + return NULL; + } + + retval->next = NULL; /* For cleanliness */ + + /* Copy the old resource to the new copy */ + retval->type = rsrc->type; + retval->id = rsrc->id; + retval->offset = rsrc->offset; + + /* Allocate space for the record data itself */ + if ((retval->data = (ubyte *) malloc(rsrc->data_len)) == NULL) + { + fprintf(stderr, _("%s: can't allocate resource data for " + "\"%.*s\".\n"), + "pdb_CopyResource", + PDB_DBNAMELEN, db->name); + free(retval); + return NULL; + } + + /* Copy the resource data */ + retval->data_len = rsrc->data_len; + memcpy(retval->data, rsrc->data, retval->data_len); + + return retval; /* Success */ +} + +/*** Helper functions ***/ + +/* get_file_length + * Return the length of a file, in bytes. In case of error, returns ~0. + */ +static uword +get_file_length(int fd) +{ + off_t here; + off_t eof; + + /* Get the current position within the file */ + here = lseek(fd, 0L, SEEK_CUR); + if (here < 0) + /* The file isn't seekable, presumably either because it + * isn't open, or because it's a pipe/socket/FIFO/tty. + */ + return ~0; + + /* Go to the end of the file */ + eof = lseek(fd, 0L, SEEK_END); + + /* And return to where we were before */ + lseek(fd, here, SEEK_SET); + + return eof - here; +} + +/* pdb_LoadHeader + * Read the header of a pdb file, and fill in the appropriate fields in + * 'db'. + */ +int +pdb_LoadHeader(int fd, + struct pdb *db) +{ + int err; + static ubyte buf[PDB_HEADER_LEN]; + /* Buffer to hold the file header */ + const ubyte *rptr; /* Pointer into buffers, for reading */ + + /* Read the header */ + if ((err = read(fd, buf, PDB_HEADER_LEN)) != PDB_HEADER_LEN) + { + perror("pdb_LoadHeader: read"); + return -1; + } + + /* Parse the database header */ + rptr = buf; + memcpy(db->name, buf, PDB_DBNAMELEN); + rptr += PDB_DBNAMELEN; + db->attributes = get_uword(&rptr); + db->version = get_uword(&rptr); + db->ctime = get_udword(&rptr); + db->mtime = get_udword(&rptr); + db->baktime = get_udword(&rptr); + db->modnum = get_udword(&rptr); + db->appinfo_offset = get_udword(&rptr); + db->sortinfo_offset = get_udword(&rptr); + db->type = get_udword(&rptr); + db->creator = get_udword(&rptr); + db->uniqueIDseed = get_udword(&rptr); + + PDB_TRACE(5) + { + time_t t; + + fprintf(stderr, "\tname: \"%s\"\n", db->name); + fprintf(stderr, "\tattributes: 0x%04x", db->attributes); + if (db->attributes & PDB_ATTR_RESDB) + fprintf(stderr, " RESDB"); + if (db->attributes & PDB_ATTR_RO) fprintf(stderr, " RO"); + if (db->attributes & PDB_ATTR_APPINFODIRTY) + fprintf(stderr, " APPINFODIRTY"); + if (db->attributes & PDB_ATTR_BACKUP) + fprintf(stderr, " BACKUP"); + if (db->attributes & PDB_ATTR_OKNEWER) + fprintf(stderr, " OKNEWER"); + if (db->attributes & PDB_ATTR_RESET) fprintf(stderr, " RESET"); + if (db->attributes & PDB_ATTR_NOCOPY) + fprintf(stderr, " NOCOPY"); + if (db->attributes & PDB_ATTR_STREAM) + fprintf(stderr, " STREAM"); + if (db->attributes & PDB_ATTR_OPEN) + fprintf(stderr, " OPEN"); + fprintf(stderr, "\n"); + fprintf(stderr, "\tversion: %u\n", db->version); + t = db->ctime - EPOCH_1904; + fprintf(stderr, "\tctime: %lu %s", db->ctime, + ctime(&t)); + t = db->mtime - EPOCH_1904; + fprintf(stderr, "\tmtime: %lu %s", db->mtime, + ctime(&t)); + t = db->baktime - EPOCH_1904; + fprintf(stderr, "\tbaktime: %lu %s", db->baktime, + ctime(&t)); + fprintf(stderr, "\tmodnum: %ld\n", db->modnum); + fprintf(stderr, "\tappinfo_offset: 0x%08lx\n", + db->appinfo_offset); + fprintf(stderr, "\tsortinfo_offset: 0x%08lx\n", + db->sortinfo_offset); + fprintf(stderr, "\ttype: '%c%c%c%c' (0x%08lx)\n", + (char) (db->type >> 24) & 0xff, + (char) (db->type >> 16) & 0xff, + (char) (db->type >> 8) & 0xff, + (char) db->type & 0xff, + db->type); + fprintf(stderr, "\tcreator: '%c%c%c%c' (0x%08lx)\n", + (char) (db->creator >> 24) & 0xff, + (char) (db->creator >> 16) & 0xff, + (char) (db->creator >> 8) & 0xff, + (char) db->creator & 0xff, + db->creator); + fprintf(stderr, "\tuniqueIDseed: %ld\n", db->uniqueIDseed); + } + + return 0; /* Success */ +} + +/* pdb_LoadRecListHeader + * Load the record list header from a pdb file, and fill in the appropriate + * fields in 'db'. + */ +static int +pdb_LoadRecListHeader(int fd, + struct pdb *db) +{ + int err; + static ubyte buf[PDB_RECORDLIST_LEN]; + const ubyte *rptr; /* Pointer into buffers, for reading */ + + /* Read the record list header */ + if ((err = read(fd, buf, PDB_RECORDLIST_LEN)) != PDB_RECORDLIST_LEN) + { + perror("pdb_LoadRecListHeader: read2"); + return -1; + } + + /* Parse the record list */ + rptr = buf; + db->next_reclistID = get_udword(&rptr); + db->numrecs = get_uword(&rptr); + + PDB_TRACE(6) + { + fprintf(stderr, "\tnextID: %ld\n", db->next_reclistID); + fprintf(stderr, "\tlen: %u\n", db->numrecs); + } + + return 0; +} + +/* pdb_LoadRsrcIndex + * Read the resource index from a resource database file, and fill in the + * appropriate fields in 'db'. + */ +static int +pdb_LoadRsrcIndex(int fd, + struct pdb *db) +{ + int i; + int err; + uword totalrsrcs; /* The real number of resources in the + * database. + */ + + totalrsrcs = db->numrecs; /* Get the number of resources in + * the database. It is necessary to + * remember this here because + * pdb_AppendResource() increments + * db->numrecs in the name of + * convenience. + */ + + if (totalrsrcs == 0) + { + /* There are no resources in this file */ + db->rec_index.rsrc = NULL; + return 0; + } + + /* Read the resource index */ + for (i = 0; i < totalrsrcs; i++) + { + static ubyte inbuf[PDB_RESOURCEIX_LEN]; + /* Input buffer */ + const ubyte *rptr; /* Pointer into buffers, for reading */ + struct pdb_resource *rsrc; + /* New resource entry */ + + /* Allocate the resource entry */ + if ((rsrc = (struct pdb_resource *) + malloc(sizeof(struct pdb_resource))) + == NULL) + return -1; + /* Scribble zeros all over it, just in case */ + memset((void *) rsrc, 0, sizeof(struct pdb_resource)); + + /* Read the next resource index entry */ + if ((err = read(fd, inbuf, PDB_RESOURCEIX_LEN)) != + PDB_RESOURCEIX_LEN) + return -1; + + /* Parse it */ + rptr = inbuf; + rsrc->type = get_udword(&rptr); + rsrc->id = get_uword(&rptr); + rsrc->offset = get_udword(&rptr); + + PDB_TRACE(6) + { + fprintf(stderr, + "\tResource %d: type '%c%c%c%c' (0x%08lx), " + "id %u, offset 0x%08lx\n", + i, + (char) (rsrc->type >> 24) & 0xff, + (char) (rsrc->type >> 16) & 0xff, + (char) (rsrc->type >> 8) & 0xff, + (char) rsrc->type & 0xff, + rsrc->type, + rsrc->id, + rsrc->offset); + } + + /* Append the new resource to the list */ + pdb_AppendResource(db, rsrc); /* XXX - Error-checking */ + db->numrecs = totalrsrcs; /* Kludge */ + } + + return 0; +} + +/* pdb_LoadRecIndex + * Read the record index from a record database file, and fill in the + * appropriate fields in 'db'. + */ +static int +pdb_LoadRecIndex(int fd, + struct pdb *db) +{ + int i; + int err; + uword totalrecs; /* The real number of records in the + * database. + */ + + totalrecs = db->numrecs; /* Get the number of records in the + * database. It is necessary to + * remember this here because + * pdb_AppendResource() increments + * db->numrecs in the name of + * convenience. + */ + + if (totalrecs == 0) + { + /* There are no records in this file */ + db->rec_index.rec = NULL; + return 0; + } + + /* Read the record index */ + /* XXX - It would be a Good Thing to check for zero-length records + * here. They've been known to appear as a result of a broken + * conduit. + */ + for (i = 0; i < totalrecs; i++) + { + static ubyte inbuf[PDB_RECORDIX_LEN]; + /* Input buffer */ + const ubyte *rptr; /* Pointer into buffers, for reading */ + struct pdb_record *rec; + /* New record entry */ + ubyte attributes; /* Combined flags+category field */ + + /* Allocate the record entry */ + if ((rec = (struct pdb_record *) + malloc(sizeof(struct pdb_record))) + == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "pdb_LoadRecIndex"); + return -1; + } + + /* Scribble zeros all over it, just in case */ + memset((void *) rec, 0, sizeof(struct pdb_record)); + + /* Read the next record index entry */ + if ((err = read(fd, inbuf, PDB_RECORDIX_LEN)) != + PDB_RECORDIX_LEN) + { + fprintf(stderr, _("%s: error reading record index " + "entry for \"%.*s\" (%d bytes): " + "%d.\n"), + "LoadRecIndex", + PDB_DBNAMELEN, db->name, + PDB_RECORDIX_LEN, + err); + perror("read"); + free(rec); + return -1; + } + + /* Parse it */ + rptr = inbuf; + rec->offset = get_udword(&rptr); + attributes = get_ubyte(&rptr); + split_attributes(attributes, &(rec->flags), &(rec->category)); + + rec->id = + ((udword) (get_ubyte(&rptr) << 16)) | + ((udword) (get_ubyte(&rptr) << 8)) | + ((udword) get_ubyte(&rptr)); + + PDB_TRACE(6) + fprintf(stderr, + "\tRecord %d: offset 0x%08lx, flags 0x%02x, " + " category 0x%02x, ID 0x%08lx\n", + i, + rec->offset, + rec->flags, + rec->category, + rec->id); + + /* Append the new record to the database */ + pdb_AppendRecord(db, rec); /* XXX - Error-checking */ + db->numrecs = totalrecs; /* Kludge */ + } + + return 0; +} + +/* pdb_LoadAppBlock + * Read the AppInfo block from a database file, and fill in the appropriate + * fields in 'db'. If the file doesn't have an AppInfo block, set it to + * NULL. + */ +static int +pdb_LoadAppBlock(int fd, + struct pdb *db) +{ + int err; + localID next_off; /* Offset of the next thing in the file + * after the AppInfo block */ + off_t offset; /* Offset into file, for checking */ + + /* Check to see if there even *is* an AppInfo block */ + if (db->appinfo_offset == 0L) + { + /* Nope */ + db->appinfo_len = 0L; + db->appinfo = NULL; + return 0; + } + + /* Figure out how long the AppInfo block is, by comparing its + * offset to that of the next thing in the file. + */ + if (db->sortinfo_offset > 0L) + /* There's a sort block */ + next_off = db->sortinfo_offset; + else if (db->numrecs > 0) + { + /* There's no sort block, but there are records. Get the + * offset of the first one. + */ + if (IS_RSRC_DB(db)) + next_off = db->rec_index.rsrc->offset; + else + next_off = db->rec_index.rec->offset; + } else + /* There is neither sort block nor records, so the AppInfo + * block must go to the end of the file. + */ + next_off = db->file_size; + + /* Subtract the AppInfo block's offset from that of the next thing + * in the file to get the AppInfo block's length. + */ + db->appinfo_len = next_off - db->appinfo_offset; + + /* This is probably paranoid, but what the hell */ + if (db->appinfo_len == 0L) + { + /* An effective no-op */ + db->appinfo = NULL; + return 0; + } + + /* Now that we know the length of the AppInfo block, allocate space + * for it and read it. + */ + if ((db->appinfo = (ubyte *) malloc(db->appinfo_len)) == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "pdb_LoadAppBlock"); + return -1; + } + + /* Just out of paranoia, make sure we're at the correct offset in + * the file. Since the two NULs may or may not have appeared in the + * file, the only thing that it makes sense to check is whether + * we've already passed the beginning of the AppInfo block, as + * given by its offset in the header. + */ + offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are */ + if (offset != db->appinfo_offset) + { + if (offset > db->appinfo_offset) + { + /* Oops! We're in the wrong place */ + fprintf(stderr, _("Warning: AppInfo block in \"%.*s\" " + "isn't where I thought it would " + "be.\n" + "Expected 0x%lx, but we're at " + "0x%lx.\n"), + PDB_DBNAMELEN, db->name, + db->appinfo_offset, (long) offset); + } + + /* Try to recover */ + offset = lseek(fd, db->appinfo_offset, SEEK_SET); + /* Go to where the AppInfo block + * ought to be */ + if (offset < 0) + { + /* Something's wrong */ + fprintf(stderr, _("Can't find the AppInfo block in " + "\"%.*s\"!\n"), + PDB_DBNAMELEN, db->name); + return -1; + } + } + + /* Read the AppInfo block */ + if ((err = read(fd, db->appinfo, db->appinfo_len)) != db->appinfo_len) + { + perror("pdb_LoadAppBlock: read"); + return -1; + } + PDB_TRACE(6) + debug_dump(stderr, "appinfo, db->appinfo_len); + + return 0; +} + +/* pdb_LoadSortBlock + * Read the sort block from a database file, and fill in the appropriate + * fields in 'db'. If the file doesn't have a sort block, set it to NULL. + * + * XXX - Largely untested, since not that many databases have sort blocks. + * But it's basically just a clone of pdb_LoadAppBlock(), so it should be + * okay. + */ +static int +pdb_LoadSortBlock(int fd, + struct pdb *db) +{ + int err; + localID next_off; /* Offset of the next thing in the file + * after the sort block */ + off_t offset; /* Offset into file, for checking */ + + /* Check to see if there even *is* a sort block */ + if (db->sortinfo_offset == 0L) + { + /* Nope */ + db->sortinfo_len = 0L; + db->sortinfo = NULL; + return 0; + } + + /* Figure out how long the sort block is, by comparing its + * offset to that of the next thing in the file. + */ + if (db->numrecs > 0) + { + /* There are records. Get the offset of the first one. + */ + if (IS_RSRC_DB(db)) + next_off = db->rec_index.rsrc->offset; + else + next_off = db->rec_index.rec->offset; + } else + /* There are no records, so the sort block must go to the + * end of the file. + */ + next_off = db->file_size; + + /* Subtract the sort block's offset from that of the next thing + * in the file to get the sort block's length. + */ + db->sortinfo_len = next_off - db->sortinfo_offset; + + /* This is probably paranoid, but what the hell */ + if (db->sortinfo_len == 0L) + { + /* An effective no-op */ + db->sortinfo = NULL; + return 0; + } + + /* Now that we know the length of the sort block, allocate space + * for it and read it. + */ + if ((db->sortinfo = (ubyte *) malloc(db->sortinfo_len)) == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "pdb_LoadSortBlock"); + return -1; + } + + /* Just out of paranoia, make sure we're at the correct offset in + * the file. Since the two NULs may or may not have appeared in the + * file, the only thing that it makes sense to check is whether + * we've already passed the beginning of the sort block, as given + * by its offset in the header. + */ + offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are */ + if (offset != db->sortinfo_offset) + { + if (offset > db->sortinfo_offset) + { + /* Oops! We're in the wrong place */ + fprintf(stderr, _("Warning: sort block in \"%.*s\" " + "isn't where I thought it would " + "be.\n" + "Expected 0x%lx, but we're at " + "0x%lx.\n"), + PDB_DBNAMELEN, db->name, + db->sortinfo_offset, (long) offset); + } + + /* Try to recover */ + offset = lseek(fd, db->sortinfo_offset, SEEK_SET); + /* Go to where the sort block + * ought to be */ + if (offset < 0) + { + /* Something's wrong */ + fprintf(stderr, _("Can't find the sort block in " + "\"%.*s\"!\n"), + PDB_DBNAMELEN, db->name); + return -1; + } + } + + /* Read the sort block */ + if ((err = read(fd, db->sortinfo, db->sortinfo_len)) != + db->sortinfo_len) + { + perror("pdb_LoadSortBlock: read"); + return -1; + } + PDB_TRACE(6) + debug_dump(stderr, "sortinfo, db->sortinfo_len); + + return 0; +} + +/* pdb_LoadResources + * Read each resource in turn from a resource database file. + */ +static int +pdb_LoadResources(int fd, + struct pdb *db) +{ + int i; + int err; + struct pdb_resource *rsrc; + + /* This assumes that the resource list has already been created by + * 'pdb_LoadRsrcIndex()'. + */ + for (i = 0, rsrc = db->rec_index.rsrc; + i < db->numrecs; + i++, rsrc = rsrc->next) + { + off_t offset; /* Current offset, for checking */ + udword next_off; /* Offset of next resource in file */ + + /* Sanity check: make sure we haven't stepped off the end + * of the list. + */ + if (rsrc == NULL) + { + fprintf(stderr, _("Hey! I can't find the %dth " + "resource in \"%.*s\"!\n"), + i, + PDB_DBNAMELEN, db->name); + return -1; + } + + PDB_TRACE(5) + fprintf(stderr, + "Reading resource %d (type '%c%c%c%c')\n", + i, + (char) (rsrc->type >> 24) & 0xff, + (char) (rsrc->type >> 16) & 0xff, + (char) (rsrc->type >> 8) & 0xff, + (char) rsrc->type & 0xff); + + /* Out of paranoia, make sure we're in the right place. + * Since the two NULs may or may not have appeared in the + * file, the only thing that it makes sense to check is + * whether we've already passed the beginning of the + * resource, as given by its offset in the resource index. + */ + offset = lseek(fd, 0L, SEEK_CUR); + /* Find out where we are now */ + if (offset != rsrc->offset) + { + if (offset > rsrc->offset) + { + fprintf(stderr, _("Warning: resource %d in " + "\"%.*s\" isn't where " + "I thought it would be.\n" + "Expected 0x%lx, but we're " + "at 0x%lx.\n"), + i, + PDB_DBNAMELEN, db->name, + rsrc->offset, (long) offset); + } + + /* Try to recover */ + offset = lseek(fd, rsrc->offset, SEEK_SET); + /* Go to where this + * resource ought to be. + */ + if (offset < 0) + { + /* Something's wrong */ + fprintf(stderr, _("Can't find resource %d in " + "\"%.*s\".\n"), + i, + PDB_DBNAMELEN, db->name); + return -1; + } + } + + /* Okay, now that we're in the right place, find out what + * the next thing in the file is: its offset will tell us + * how much to read. + * It's debatable whether 'i' or 'rsrc' should be + * authoritative for determining the offset of the next + * resource. I'm going to choose 'rsrc', since I think + * that's more likely to be immune to fencepost errors. The + * two should, however, be equivalent. In fact, it might be + * a Good Thing to add a check to make sure. + */ + if (rsrc->next == NULL) + { + /* This is the last resource in the file, so it + * goes to the end of the file. + */ + next_off = db->file_size; + } else { + /* This isn't the last resource. Find the next + * one's offset. + */ + next_off = rsrc->next->offset; + } + + /* Subtract this resource's index from that of the next + * thing, to get the size of this resource. + */ + rsrc->data_len = next_off - rsrc->offset; + + /* Allocate space for this resource */ + if ((rsrc->data = (ubyte *) malloc(rsrc->data_len)) == NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "pdb_LoadResources"); + return -1; + } + + /* Read the resource */ + if ((err = read(fd, rsrc->data, rsrc->data_len)) != + rsrc->data_len) + { + fprintf(stderr, _("Can't read resource %d in " + "\"%.*s\".\n"), + i, + PDB_DBNAMELEN, db->name); + perror("pdb_LoadResources: read"); + return -1; + } + PDB_TRACE(6) + { + fprintf(stderr, "Contents of resource %d:\n", i); + debug_dump(stderr, "data, + rsrc->data_len); + } + } + + return 0; /* Success */ +} + +/* pdb_LoadRecords + * Read each record in turn from a record database file. + */ +static int +pdb_LoadRecords(int fd, + struct pdb *db) +{ + int i; + int err; + struct pdb_record *rec; + + /* This assumes that the record list has already been created by + * 'pdb_LoadRecIndex()'. + */ + for (i = 0, rec = db->rec_index.rec; + i < db->numrecs; + i++, rec = rec->next) + { + off_t offset; /* Current offset, for checking */ + localID next_off; /* Offset of next resource in file */ + + /* Sanity check: make sure we haven't stepped off the end + * of the list. + */ + if (rec == NULL) + { + fprintf(stderr, _("Hey! I can't find the %dth " + "record in \"%.*s\"!\n"), + i, + PDB_DBNAMELEN, db->name); + return -1; + } + + PDB_TRACE(5) + fprintf(stderr, "Reading record %d (id 0x%08lx)\n", + i, rec->id); + + /* Out of paranoia, make sure we're in the right place. + * Since the two NULs may or may not have appeared in the + * file, the only thing that it makes sense to check is + * whether we've already passed the beginning of the + * record, as given by its offset in the record index. + */ + offset = lseek(fd, 0L, SEEK_CUR); + /* Find out where we are now */ + if (offset != rec->offset) + { + if (offset > rec->offset) + { + fprintf(stderr, _("Warning: record %d in " + "\"%.*s\" isn't where " + "I thought it would be.\n" + "Expected 0x%lx, but we're " + "at 0x%lx.\n"), + i, + PDB_DBNAMELEN, db->name, + rec->offset, (long) offset); + } + + /* Try to recover */ + offset = lseek(fd, rec->offset, SEEK_SET); + /* Go to where this record + * ought to be. */ + if (offset < 0) + { + /* Something's wrong */ + fprintf(stderr, _("Can't find record %d in " + "\"%.*s\".\n"), + i, + PDB_DBNAMELEN, db->name); + return -1; + } + } + + /* Okay, now that we're in the right place, find out what + * the next thing in the file is: its offset will tell us + * how much to read. + * It's debatable whether 'i' or 'rec' should be + * authoritative for determining the offset of the next + * resource. I'm going to choose 'rec', since I think + * that's more likely to be immune from fencepost errors. + * The two should, however, be equivalent. In fact, it + * might be a Good Thing to add a check to make sure. + */ + if (rec->next == NULL) + { + /* This is the last record in the file, so it goes + * to the end of the file. + */ + next_off = db->file_size; + } else { + /* This isn't the last record. Find the next one's + * offset. + */ + next_off = rec->next->offset; + } + + /* Subtract this record's index from that of the next one, + * to get the size of this record. + */ + rec->data_len = next_off - rec->offset; + + /* Allocate space for this record + * If there's a record with length zero, don't pass that to + * malloc(). This is most likely due to a broken conduit. + * XXX - The Right Thing to do would be not to read + * zero-length records, but that would involve fixing the + * record index. + */ + if (rec->data_len > 0) + { + if ((rec->data = (ubyte *) malloc(rec->data_len)) == + NULL) + { + fprintf(stderr, _("%s: Out of memory.\n"), + "pdb_LoadRecords"); + return -1; + } + + /* Read the record */ + if ((err = read(fd, rec->data, rec->data_len)) != + rec->data_len) + { + fprintf(stderr, _("Can't read record %d in " + "\"%.*s\".\n"), + i, + PDB_DBNAMELEN, db->name); + perror("pdb_LoadRecords: read"); + return -1; + } + + PDB_TRACE(6) + { + fprintf(stderr, "Contents of record %d:\n", i); + debug_dump(stderr, "data, + rec->data_len); + } + } + } + + return 0; /* Success */ +} + + /* This is for Emacs's benefit: + * Local Variables: *** + * fill-column: 75 *** + * End: *** + */ diff --git a/coldsync/pdb.h b/coldsync/pdb.h new file mode 100644 index 000000000..1d34d23c4 --- /dev/null +++ b/coldsync/pdb.h @@ -0,0 +1,273 @@ +/* pdb.h + * + * Definitions and such for Palm databases. + * + * Copyright (C) 1999-2000, Andrew Arensburger. + * You may distribute this file under the terms of the Artistic + * License, as specified in the README file. + * + * $Id: pdb.h,v 1.1 2002/08/16 06:13:10 robertl Exp $ + */ +#ifndef _pdb_h_ +#define _pdb_h_ + +/* XXX - Add a type (and support functions) for those ubitquitous + * 4-character IDs. + */ + +#define EPOCH_1904 2082844800L /* Difference, in seconds, between + * Palm's epoch (Jan. 1, 1904) and + * Unix's epoch (Jan. 1, 1970). + */ + +#define PDB_DBNAMELEN 32 /* Length of name field in database + * header */ + +/* Database attribute flags */ +#define PDB_ATTR_RESDB 0x0001 /* This is a resource database. + * Resource databases are usually + * saved in files with ".prc" + * extensions. Other databases are + * saved with a ".pdb" extension. + */ +#define PDB_ATTR_RO 0x0002 /* Read-only database */ +#define PDB_ATTR_APPINFODIRTY 0x0004 /* App info block is dirty */ +#define PDB_ATTR_BACKUP 0x0008 /* Back up the database if no + * app-specific conduit exists */ +#define PDB_ATTR_OKNEWER 0x0010 /* Tells the backup conduit that + * it's okay to install a newer + * version of this database with a + * different name if this one is + * open. Usually used for the + * Graffiti Shortcuts database. + */ +#define PDB_ATTR_RESET 0x0020 /* Reset the Palm after the + * database is installed */ +#define PDB_ATTR_NOCOPY 0x0040 /* Database should not be copied(?) */ +#define PDB_ATTR_STREAM 0x0080 /* Database is used for file stream + * implementation(?). + */ +#define PDB_ATTR_OPEN 0x8000 /* Database is open */ + +/* Record attributes + * These are the attributes that individual records in a database can have. + * I've taken the liberty of giving them different names from Palm's, since + * Palm's names are rather confusing. + * + * PDB_REC_PRIVATE is set on a record that has been marked "private" by the + * user. It is not encrypted, and if the desktop asks for this record, the + * Palm will not refuse or ask for a password. In short, the Palm needs to + * trust the desktop. + * + * PDB_REC_DIRTY is set on a record whose contents have been modified since + * the last sync. If the user deletes a record without modifying it, + * PDB_REC_DIRTY will not be set, but if he modifies it, then deletes it, + * then both PDB_REC_DIRTY and PDB_REC_DELETED will be set. + * + * PDB_REC_DELETED is set on a record that has been deleted by the user + * since the last sync. Unfortunately, it looks as if not all applications + * are polite enough to set this flag, so you have to go with + * PDB_REC_ARCHIVE and PDB_REC_EXPUNGED. + * + * If the user chose the "Save archive copy on PC" option when deleting a + * record, then the PDB_REC_ARCHIVE bit will be set on the record (with any + * luck, so will PDB_REC_DELETED). + * + * If the user did not choose the "Save archive copy on PC" option when + * deleting a record, the PDB_REC_EXPUNGED bit will be set on the record + * (as will PDB_REC_DELETED, perhaps). Apparently, what happens is this: + * when the user deletes a record, a copy is left around so that HotSync + * will know to delete this record. However, if the user chose not to keep + * a copy, then, in order to conserve memory, the Palm will delete the + * record data, although it will keep a copy of the record header for + * HotSync. + */ +#define PDB_REC_EXPUNGED 0x80 /* The contents of this record have + * been deleted, leaving only the + * record info. (Palm calls this + * 'dlpRecAttrDeleted'.) + */ +#define PDB_REC_DIRTY 0x40 /* Record has been modified. (Palm + * calls this 'dlpRecAttrDirty'.) + */ +#define PDB_REC_DELETED 0x20 /* This record has been deleted. + * (Palm calls this + * 'dlpRecAttrBusy'.) + */ +#define PDB_REC_PRIVATE 0x10 /* Record is private: don't show to + * anyone without asking for a + * password. (Palm calls this + * 'dlpRecAttrSecret'.) + */ +#define PDB_REC_ARCHIVE 0x08 /* This record should be archived + * at the next sync. (Palm calls + * this 'dlpRecAttrArchived'.) + */ + +typedef udword localID; /* Local (card-relative) chunk ID + * (basically, a pointer that can + * be used as a unique ID). + */ + +#define PDB_HEADER_LEN 72 /* Length of header in a file */ +#define PDB_RECORDLIST_LEN 6 /* Length of record index header in + * file */ + +/* pdb_record + * A plain old record, containing arbitrary data. + */ +struct pdb_record +{ + struct pdb_record *next; /* Next record on linked list */ + localID offset; /* Offset of record in file */ + ubyte flags; /* Record flags (PDB_REC_*) */ + ubyte category; /* Record's category */ + udword id; /* Record's unique ID. Actually, + * only the bottom 3 bytes are + * stored in the file, but for + * everything else, it's much + * easier to just consider this a + * 32-bit integer. + */ + uword data_len; /* Length of this record */ + ubyte *data; /* This record's data */ +}; +#define PDB_RECORDIX_LEN 8 /* Size of a pdb_record in a file */ + +/* pdb_resource + * Mac hackers should feel at home here: the type of a resource is really a + * 4-character category identifier, and the ID is an integer within that + * category. + */ +struct pdb_resource +{ + struct pdb_resource *next; /* Next resource on linked list */ + udword type; /* Resource type */ + uword id; /* Resource ID */ + localID offset; /* Offset of resource in file */ + uword data_len; /* Length of this resource */ + ubyte *data; /* This resource's data */ +}; +#define PDB_RESOURCEIX_LEN 10 /* Size of a pdb_resource in a file */ + +/* pdb + * Structure of a Palm database (file), both resource databases (.prc) and + * record databases (.pdb). + */ +struct pdb +{ + long file_size; /* Total length of file */ + + char name[PDB_DBNAMELEN]; /* Database name */ + uword attributes; /* Database attributes */ + uword version; /* Database version */ + + udword ctime; /* Creation time */ + udword mtime; /* Time of last modification */ + udword baktime; /* Time of last backup */ + udword modnum; /* Modification number */ + /* XXX - What exactly is the modification number? + * Does it get incremented each time you make any + * kind of change to the database? + */ + localID appinfo_offset; /* Offset of AppInfo block in the + * file */ + localID sortinfo_offset; /* Offset of sort block in the file */ + + udword type; /* Database type */ + udword creator; /* Database creator */ + + udword uniqueIDseed; /* Used to generate unique IDs for + * records and resources. Only the + * lower 3 bytes are used. The high + * byte is for alignment. + */ + + localID next_reclistID; /* ID of next record index in the + * file. In practice, this field is + * always zero. + */ + uword numrecs; /* Number of records/resources in + * the file. + */ + + long appinfo_len; /* Length of AppInfo block */ + void *appinfo; /* Optional AppInfo block */ + long sortinfo_len; /* Length of sort block */ + void *sortinfo; /* Optional sort block */ + + /* Record/resource list. Each of these is actually a linked list, + * to make it easy to insert and delete records. + */ + union { + struct pdb_record *rec; + struct pdb_resource *rsrc; + } rec_index; +}; + +/* Convenience macros */ +#define IS_RSRC_DB(db) ((db)->attributes & PDB_ATTR_RESDB) + /* Is this a resource database? If + * not, it must be a record + * database. + */ + +extern int pdb_trace; /* Debugging level for PDB stuff */ + +extern struct pdb *new_pdb(); +extern void free_pdb(struct pdb *db); +extern void pdb_FreeRecord(struct pdb_record *rec); +extern void pdb_FreeResource(struct pdb_resource *rsrc); +extern struct pdb *pdb_Read(int fd); /* Load a pdb from a file. */ +extern int pdb_Write(const struct pdb *db, int fd); + /* Write a pdb to a file */ +extern struct pdb_record *pdb_FindRecordByID( + const struct pdb *db, + const udword id); +extern struct pdb_record *pdb_FindRecordByIndex( + const struct pdb *db, + const uword index); +extern int pdb_DeleteRecordByID( + struct pdb *db, + const udword id); +extern int pdb_AppendRecord(struct pdb *db, struct pdb_record *newrec); +extern int pdb_AppendResource(struct pdb *db, struct pdb_resource *newrsrc); +extern int pdb_InsertRecord( + struct pdb *db, + struct pdb_record *prev, + struct pdb_record *newrec); +extern int pdb_InsertResource( + struct pdb *db, + struct pdb_resource *prev, + struct pdb_resource *newrsrc); +extern struct pdb_record *new_Record( + const ubyte attributes, + const ubyte category, + const udword id, + const uword len, + const ubyte *data); +extern struct pdb_resource *new_Resource( + const udword type, + const uword id, + const uword len, + const ubyte *data); +extern struct pdb_record *pdb_CopyRecord( + const struct pdb *db, + const struct pdb_record *rec); +extern struct pdb_resource *pdb_CopyResource( + const struct pdb *db, + const struct pdb_resource *rsrc); +extern int pdb_LoadHeader(int fd, struct pdb *db); + +/* XXX - Functions to write: +pdb_setAppInfo set the appinfo block +pdb_setSortInfo set the sortinfo block +*/ + +#endif /* _pdb_h_ */ + + /* This is for Emacs's benefit: + * Local Variables: *** + * fill-column: 75 *** + * End: *** + */ diff --git a/coldsync/util.c b/coldsync/util.c new file mode 100644 index 000000000..21b1f90b4 --- /dev/null +++ b/coldsync/util.c @@ -0,0 +1,290 @@ +/* util.c + * + * Misc. utility functions. + * + * Copyright (C) 1999, Andrew Arensburger. + * You may distribute this file under the terms of the Artistic + * License, as specified in the README file. + * + * The get_*() functions are used to extract values out of strings of + * ubytes and convert them to the native format. + * The put_*() functions, conversely, are used to take a value in the + * native format, convert them to Palm (big-endian) format, and write + * them to a ubyte string. + * + * $Id: util.c,v 1.1 2002/08/16 06:13:10 robertl Exp $ + */ + +#include "config.h" +#include +#include /* For isprint() */ +#include + +#ifndef EPOCH_1904 +# define EPOCH_1904 2082844800L /* Difference, in seconds, between + * Palm's epoch (Jan. 1, 1904) and + * Unix's epoch (Jan. 1, 1970). + */ +#endif /* EPOCH_1904 */ + +/* XXX - Most of the functions below really ought to be inlined. Not sure + * how to do this portably, though. + */ + +INLINE ubyte +peek_ubyte(const ubyte *buf) +{ + return buf[0]; +} + +INLINE uword +peek_uword(const ubyte *buf) +{ + return ((uword) buf[0] << 8) | + buf[1]; +} + +INLINE udword +peek_udword(const ubyte *buf) +{ + return ((uword) buf[0] << 24) | + ((uword) buf[1] << 16) | + ((uword) buf[2] << 8) | + buf[3]; +} + +INLINE ubyte +get_ubyte(const ubyte **buf) +{ + ubyte retval; + + retval = peek_ubyte(*buf); + *buf += SIZEOF_UBYTE; + + return retval; +} + +INLINE void +put_ubyte(ubyte **buf, ubyte value) +{ + **buf = value; + ++(*buf); +} + +INLINE uword +get_uword(const ubyte **buf) +{ + uword retval; + + retval = peek_uword(*buf); + *buf += SIZEOF_UWORD; + + return retval; +} + +INLINE void +put_uword(ubyte **buf, uword value) +{ + **buf = (value >> 8) & 0xff; + ++(*buf); + **buf = value & 0xff; + ++(*buf); +} + +INLINE udword +get_udword(const ubyte **buf) +{ + udword retval; + + retval = peek_udword(*buf); + *buf += SIZEOF_UDWORD; + + return retval; +} + +INLINE void +put_udword(ubyte **buf, udword value) +{ + **buf = (value >> 24) & 0xff; + ++(*buf); + **buf = (value >> 16) & 0xff; + ++(*buf); + **buf = (value >> 8) & 0xff; + ++(*buf); + **buf = value & 0xff; + ++(*buf); +} +#if TIME +/* XXX - Figure out the timezone hairiness: + * Palms don't have timezones. Hence, the Palm's epoch is Jan. 1, 1904 in + * the local timezone. + * Unless you're syncing across the network, in which case its epoch is + * Jan. 1, 1904 in the timezone it happens to be in (which may not be the + * same as the desktop's timezone). + * Except that there are (I'm sure) tools that add timezones to the Palm. + * These should be consulted. + * Times generated locally are in the local timezone (i.e., the one that + * the desktop machine is in). + */ + +/* time_dlp2time_t + * Convert the DLP time structure into a Unix time_t, and return it. + */ +time_t +time_dlp2time_t(const struct dlp_time *dlpt) +{ + struct tm tm; + + /* Convert the dlp_time into a struct tm, then just use mktime() to + * do the conversion. + */ + tm.tm_sec = dlpt->second; + tm.tm_min = dlpt->minute; + tm.tm_hour = dlpt->hour; + tm.tm_mday = dlpt->day; + tm.tm_mon = dlpt->month - 1; + tm.tm_year = dlpt->year - 1900; + tm.tm_wday = 0; + tm.tm_yday = 0; + tm.tm_isdst = 0; +#if HAVE_TM_ZONE + tm.tm_gmtoff = 0; + tm.tm_zone = NULL; +#else +/* XXX - ANSI doesn't allow #warning, and we're not using the timezone for + * anything yet. + */ +/* #warning You do not have tm_zone */ +#endif + + return mktime(&tm); +} + +/* time_dlp2palmtime + * Convert a DLP time structure into a Palm time_t (number of seconds since + * Jan. 1. 1904), and return it. + */ +udword +time_dlp2palmtime(const struct dlp_time *dlpt) +{ + time_t now; /* The time, as a Unix time_t */ + struct tm tm; + + /* Convert the dlp_time into a struct tm, use mktime() to do the + * conversion, and add the difference in epochs. + */ + tm.tm_sec = dlpt->second; + tm.tm_min = dlpt->minute; + tm.tm_hour = dlpt->hour; + tm.tm_mday = dlpt->day; + tm.tm_mon = dlpt->month - 1; + tm.tm_year = dlpt->year - 1900; + tm.tm_wday = 0; + tm.tm_yday = 0; + tm.tm_isdst = 0; +#if HAVE_TM_ZONE + tm.tm_gmtoff = 0; + tm.tm_zone = NULL; +#endif + + now = mktime(&tm); + now += EPOCH_1904; + + return now; +} + +/* time_time_t2dlp + * Convert a Unix time_t into a DLP time structure. Put the result in + * 'dlpt'. + */ +void +time_time_t2dlp(const time_t t, + struct dlp_time *dlpt) +{ + struct tm *tm; + + tm = localtime(&t); /* Break 't' down into components */ + + /* Copy the relevant fields over to 'dlpt' */ + dlpt->year = tm->tm_year + 1900; + dlpt->month = tm->tm_mon + 1; + dlpt->day = tm->tm_mday; + dlpt->hour = tm->tm_hour; + dlpt->minute = tm->tm_min; + dlpt->second = tm->tm_sec; +} + +/* time_palmtime2dlp + + * Convert a Palm time (seconds since the Jan. 1, 1904) to a DLP time + * structure. Put the result in 'dlpt'. + */ +void +time_palmtime2dlp(const udword palmt, + struct dlp_time *dlpt) +{ + struct tm *tm; + time_t t; + + /* Convert the Palm time to a Unix time_t */ + t = palmt - EPOCH_1904; + + /* Break the Unix time_t into components */ + tm = localtime(&t); + + /* Copy the relevant fields over to 'dlpt' */ + dlpt->year = tm->tm_year + 1900; + dlpt->month = tm->tm_mon + 1; + dlpt->day = tm->tm_mday; + dlpt->hour = tm->tm_hour; + dlpt->minute = tm->tm_min; + dlpt->second = tm->tm_sec; +} +#endif + +/* debug_dump + * Dump the contents of an array of ubytes to stderr, for debugging. + */ +void +debug_dump(FILE *outfile, const char *prefix, + const ubyte *buf, const udword len) +{ + int lineoff; + + for (lineoff = 0; lineoff < len; lineoff += 16) + { + int i; + + fprintf(outfile, "%s ", prefix); + for (i = 0; i < 16; i++) + { + if (lineoff + i < len) + { + /* Regular bytes */ + fprintf(outfile, "%02x ", buf[lineoff+i]); + } else { + /* Filler at the end of the line */ + fprintf(outfile, " "); + } + } + fprintf(outfile, " | "); + for (i = 0; i < 16; i++) + { + if (lineoff + i < len) + { + /* Regular bytes */ + if (isprint(buf[lineoff+i])) + fprintf(outfile, "%c", buf[lineoff+i]); + else + fprintf(outfile, "."); + } else + break; + } + fprintf(outfile, "\n"); + } +} + /* This is for Emacs's benefit: + * Local Variables: *** + * fill-column: 75 *** + * End: *** + */ diff --git a/mingw/Makefile b/mingw/Makefile index 962ef5abd..b8524bd19 100644 --- a/mingw/Makefile +++ b/mingw/Makefile @@ -1,4 +1,4 @@ -CC=/home/robertl/cross-tools/bin/i386-mingw32msvc-gcc -Iinclude +CC=/home/robertl/cross-tools/bin/i386-mingw32msvc-gcc -Iinclude -I../coldsync -DCETUS VPATH=.. gpsbabel.exe: @@ -7,3 +7,4 @@ include ../Makefile gpsbabel.exe: $(OBJS) $(CC) -static $(CFLAGS) $(OBJS) lib/libexpat.a -o gpsbabel.exe + cp gpsbabel.exe /tmp -- 2.30.2